package gamelobby;

import client.ClientPlayer;
import gameframe.Chess;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;

/**
 * @author Hsu琛君珩
 * @ClassName:GameLobby
 * @date 2023-12-22
 * @apiNote
 * @Version: v1.0
 */

/**
 * 游戏大厅界面，展示不同房间的状态，允许用户加入或观战。
 */
public class GameLobby extends JFrame {
    /**
     * GameLobby类的单例实例。
     * 用于确保全局只有一个游戏大厅实例。
     */
    private static GameLobby instance;
    /**
     * 游戏大厅的主面板。
     * 用于显示所有房间的状态和信息。
     */
    private JPanel mainPanel;
    /**
     * 滚动面板。
     * 包裹mainPanel，允许用户在有多个房间时滚动查看。
     */
    private JScrollPane scrollPane;
    /**
     * 游戏大厅默认的房间数量。
     * 初始化时，大厅将显示这么多房间。
     */
    private final int roomNum = 12;
    /**
     * 房间人数标签列表。
     * 每个标签显示对应房间的当前人数。
     */
    private ArrayList<JLabel> roomMemberNumLabels;
    /**
     * 进入房间按钮列表。
     * 每个按钮允许用户进入对应的房间。
     */
    private ArrayList<JButton> roomEnterButtons;
    /**
     * 房间成员数量列表。
     * 记录每个房间当前的人数。
     */
    private ArrayList<Integer> roomMemberNums;

    /**
     * 获取GameLobby的单例实例。
     * @return GameLobby的单例实例。
     */
    public static GameLobby getInstance() {
        if (instance == null) {
            instance = new GameLobby();
        }
        return instance;
    }

    /**
     * 构造函数，初始化游戏大厅界面。
     * 在构造函数中，初始化房间成员数量列表、房间人数标签列表和房间进入按钮列表。
     * 配置游戏大厅窗口，初始化主面板，并为每个房间创建面板。最后，配置滚动面板并显示游戏大厅。
     */
    private GameLobby() {
        roomMemberNums = ClientPlayer.getInstance().getRoomList();  // 从ClientPlayer获取当前的房间人数信息
        roomMemberNumLabels = new ArrayList<>();
        roomEnterButtons = new ArrayList<>();

        configureFrame();  // 配置游戏大厅窗口
        initializeMainPanel();  // 初始化主面板
        configureScrollPane();  // 配置滚动面板
        this.pack();  // 调整窗口以适应组件的首选大小和布局
        this.setVisible(true);  // 显示游戏大厅窗口
    }

    /**
     * 配置游戏大厅窗口的基本属性。
     * 设置游戏大厅窗口的位置、是否可调整大小以及关闭操作。
     */
    private void configureFrame() {
        this.setLocation(250, 0);  // 设置窗口的初始位置
        this.setResizable(false);  // 设置窗口不可调整大小
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);  // 设置默认关闭操作
    }

    /**
     * 初始化主面板，并为每个房间创建面板。
     * 设置主面板的首选大小、布局和背景色。然后为每个房间创建并添加一个面板到主面板。
     */
    private void initializeMainPanel() {
        mainPanel = new JPanel();
        mainPanel.setPreferredSize(new Dimension(800, 650));  // 设置主面板的首选大小
        mainPanel.setLayout(new FlowLayout());  // 设置主面板的布局
        mainPanel.setBackground(new Color(0, 98, 132));  // 设置主面板的背景色

        // 为每个房间创建并添加一个面板到主面板
        for (int i = 0; i < roomNum; i++) {
            JPanel roomPanel = createRoomPanel(i);
            mainPanel.add(roomPanel);
        }
    }

    /**
     * 配置滚动面板。
     * 创建一个滚动面板，设置其首选大小，并将其添加到游戏大厅窗口的内容面板。
     */
    private void configureScrollPane() {
        scrollPane = new JScrollPane(mainPanel);  // 创建滚动面板并包裹主面板
        scrollPane.setPreferredSize(new Dimension(800, 700));  // 设置滚动面板的首选大小
        this.getContentPane().add(scrollPane);  // 将滚动面板添加到游戏大厅窗口的内容面板
    }

    /**
     * 创建单个房间的面板。
     * 使用空布局创建一个新的JPanel，设置其大小，并添加房间的图片标签和信息面板。
     * @param roomId 房间的ID，用于标识不同的房间。
     * @return 返回配置好的房间面板对象。
     */
    private JPanel createRoomPanel(int roomId) {
        JPanel roomPanel = new JPanel();  // 创建新的JPanel对象
        roomPanel.setLayout(null);  // 设置布局为null，即使用绝对定位
        roomPanel.setPreferredSize(new Dimension(180, 210));  // 设置面板的首选大小

        JLabel imageLabel = createImageLabel();  // 创建房间的图片标签
        JPanel infoPanel = createInfoPanel(roomId);  // 创建房间的信息面板

        roomPanel.add(imageLabel);  // 将图片标签添加到房间面板
        roomPanel.add(infoPanel);  // 将信息面板添加到房间面板

        return roomPanel;  // 返回配置好的房间面板
    }

    /**
     * 创建房间图片标签。
     * 创建一个新的JLabel对象，设置其大小和图标，用于显示房间的图片。
     * @return 返回配置好的房间图片标签对象。
     */
    private JLabel createImageLabel() {
        JLabel imageLabel = new JLabel();  // 创建新的JLabel对象
        imageLabel.setBounds(0, 0, 180, 180);  // 设置标签的位置和大小
        imageLabel.setIcon(new ImageIcon("image/room.png"));  // 设置房间的图片图标
        return imageLabel;  // 返回配置好的房间图片标签
    }

    /**
     * 创建房间信息面板，包括人数标签和进入按钮。
     * 使用空布局创建一个新的JPanel，设置其大小，并添加房间人数标签和进入按钮。
     * @param roomId 房间的ID，用于标识不同的房间。
     * @return 返回配置好的房间信息面板对象。
     */
    private JPanel createInfoPanel(int roomId) {
        JPanel infoPanel = new JPanel();  // 创建新的JPanel对象
        infoPanel.setBounds(0, 180, 180, 30);  // 设置面板的位置和大小
        infoPanel.setLayout(null);  // 设置布局为null，即使用绝对定位

        JLabel clientNum = createClientNumLabel(roomId);  // 创建房间人数标签
        JButton enterButton = createEnterButton(roomId);  // 创建进入房间按钮

        infoPanel.add(clientNum);  // 将人数标签添加到信息面板
        infoPanel.add(enterButton);  // 将进入按钮添加到信息面板

        return infoPanel;  // 返回配置好的房间信息面板
    }

    /**
     * 创建显示房间人数的标签。
     * 这个标签用于显示每个房间当前的玩家数量，帮助用户了解房间的状态。
     * @param roomId 房间的ID，用于从房间成员数量列表中获取对应房间的当前人数。
     * @return 返回配置好的显示房间人数的JLabel对象。
     */
    private JLabel createClientNumLabel(int roomId) {
        String curNum = "当前人数:" + roomMemberNums.get(roomId);  // 获取并构造显示当前人数的字符串
        JLabel clientNum = new JLabel(curNum, SwingConstants.CENTER);  // 创建一个新的JLabel对象并居中显示文本
        roomMemberNumLabels.add(clientNum);  // 将新创建的标签添加到房间人数标签列表中
        clientNum.setBounds(0, 0, 90, 30);  // 设置标签的位置和大小
        clientNum.setOpaque(true);  // 设置标签背景为不透明
        clientNum.setBackground(new Color(0, 98, 132));  // 设置标签的背景颜色
        return clientNum;  // 返回配置好的房间人数标签
    }

    /**
     * 创建进入房间的按钮。
     * 用户点击这个按钮可以请求进入对应的房间，加入游戏或观战。
     * @param roomId 房间的ID，用于为按钮命名以及确定按钮的功能。
     * @return 返回配置好的进入房间的JButton对象。
     */
    private JButton createEnterButton(int roomId) {
        JButton enterButton = new JButton();  // 创建一个新的JButton对象
        enterButton.setName(Integer.toString(roomId));  // 设置按钮的名称，与房间ID对应
        enterButton.setBounds(90, 0, 90, 30);  // 设置按钮的位置和大小
        enterButton.setBackground(new Color(0, 98, 132));  // 设置按钮的背景颜色
        enterButton.addActionListener(this::enterRoomAction);  // 为按钮添加事件监听器

        updateEnterButtonText(enterButton, roomId);  // 更新按钮的文本
        roomEnterButtons.add(enterButton);  // 将新创建的按钮添加到进入房间按钮列表中
        return enterButton;  // 返回配置好的进入房间按钮
    }

    /**
     * 进入房间按钮的事件处理方法。
     * 当用户点击进入房间按钮时，此方法会被调用。它处理用户加入房间的请求。
     * @param e ActionEvent对象，包含事件的详细信息。
     */
    private void enterRoomAction(ActionEvent e) {
        JButton button = (JButton) e.getSource();  // 获取事件源（被点击的按钮）
        int buttonID = Integer.parseInt(button.getName());  // 获取按钮对应的房间ID
        int currMemberNum = updateRoomMemberNum(buttonID, 1);  // 更新并获取房间内的当前成员数量

        int chessColor = determineChessColor(currMemberNum);  // 确定玩家棋子的颜色
        ClientPlayer.getInstance().init(buttonID, chessColor);  // 初始化玩家在房间内的信息
        GameLobby.getInstance().setVisible(false);  // 隐藏游戏大厅界面
    }

    /**
     * 确定玩家棋子的颜色。
     * 根据房间内当前的玩家数量来确定新加入玩家的棋子颜色。
     * 第一个和第二个玩家分别被分配黑棋和白棋，之后加入的玩家为观战者。
     * @param currMemberNum 当前房间人数。
     * @return 返回棋子颜色，可能是黑棋、白棋或表示观战者的状态。
     */
    private int determineChessColor(int currMemberNum) {
        if (currMemberNum <= 2) {
            // 第一个玩家是黑棋，第二个玩家是白棋
            return currMemberNum == 1 ? Chess.BLACK : Chess.WHITE;
        }
        return Chess.SPACE;  // 超过两个玩家，即为观战者
    }

    /**
     * 更新进入房间按钮的文本。
     * 根据房间内的玩家数量来更新按钮文本，指示用户可以加入游戏或观战。
     * @param enterButton 进入房间按钮。
     * @param roomId 房间的ID。
     */
    private void updateEnterButtonText(JButton enterButton, int roomId) {
        int num = roomMemberNums.get(roomId);
        if (num < 2) {
            enterButton.setText("加入房间");  // 房间内玩家少于两人时，新玩家可以加入游戏
        } else {
            enterButton.setText("加入观战");  // 房间内玩家已满两人时，新玩家只能加入观战
        }
    }

    /**
     * 更新房间人数标签上的信息。
     * 当有玩家加入或离开房间时，更新显示当前房间人数的标签。
     * @param roomId 房间号。
     * @param change 变化值，正数表示增加玩家，负数表示减少玩家。
     * @return 返回更新后的当前房间中的人数。
     */
    private int updateRoomMemberNum(int roomId, int change) {
        int presentNum = roomMemberNums.get(roomId) + change;
        roomMemberNumLabels.get(roomId).setText("当前人数:" + presentNum);
        roomMemberNums.set(roomId, presentNum);  // 更新房间成员数量列表
        return presentNum;
    }

    /**
     * 启动大厅线程，不断向主服务器请求信息更新。
     * 这个方法在游戏大厅界面显示时持续运行，确保显示的房间信息是最新的。
     */
    public void start() {
        new Thread(() -> {
            while (!ClientPlayer.getInstance().isGaming()) {
                roomMemberNums = ClientPlayer.getInstance().getRoomList();  // 从主服务器获取最新的房间信息
                updateRoomInfo();  // 更新本地显示的房间信息
                try {
                    Thread.sleep(100);  // 每次更新后暂停一段时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * 更新房间人数标签和进入按钮的提示信息。
     * 遍历所有房间，更新对应的房间人数标签和进入按钮的文本。
     */
    private void updateRoomInfo() {
        for (int i = 0; i < roomNum; i++) {
            updateEnterButtonText(roomEnterButtons.get(i), i);  // 更新按钮文本
            roomMemberNumLabels.get(i).setText("当前人数:" + roomMemberNums.get(i));  // 更新房间人数标签
        }
    }

    /**
     * 主程序入口，用于测试界面。
     */
    public static void main(String[] args) {
        GameLobby.getInstance().start();
    }
}
